/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package preka.prefuse.data.io;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.AttributesImpl;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.io.DataIOException;
import preka.IVC;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;


// TODO: Melhoria: Dividir em 3 classes: ArffReader, GraphMLConverter e ArffWriter
/**
 *
 * @author vhbarros
 */
public class ArffReader {


    private Instances m_data;
    private ArrayList m_classes;
    private int m_classIndex;

    // TODO retirar isto!
    private boolean m_includeInstanceInfo = true;

    public ArffReader (){       
    }
    
    public ArffReader (String fileLocation) throws DataIOException{
        
        // Carrega o ficheiro .arff nas estruturas da classe
        readArff(fileLocation);
        
    }

    public Instances getData() {
        return m_data;
    }

    public Graph readArff(String fileLocation, ArrayList relationalData) throws DataIOException {

        // Carrega as estruras da classe atrevés da leitura do ficheiro .arff
        readArff(fileLocation);

        // Devolve o grafo gerado com base nas estruturas carregadas
        return getGraph(relationalData);

    }

    public Graph getGraph(ArrayList relationalData) {

        // Criação do grafo
        Graph g = new Graph();

        // Definição dos atributos e respectivos tipos
        g.addColumn(IVC.INSTANCE_ATTRIBUTE, int.class);
        g.addColumn(IVC.EDGE_TYPE_ATTRIBUTE, String.class);
        for (int i=0; i < m_data.numAttributes(); i++) {
            Attribute atr = m_data.attribute(i);
            g.addColumn(atr.name(), String.class);
        }

        // Criação dos nós dos objectos, com a definição dos respectivos atributos
        for (int i=0; i < m_data.numInstances(); i++)
        {
            Instance ins = m_data.instance(i);
            Node n = g.addNode();
            n.set(IVC.INSTANCE_ATTRIBUTE, i);

            for (int j=0; j < m_data.numAttributes(); j++) {
                Attribute atr = ins.attribute(j);
                n.set(atr.name(), ins.toString(j));
            }
        }

        // Se for fornecida informação relacional, serão criados os arcos correspondentes
        if(relationalData != null) {
            Iterator i = relationalData.iterator();
            while (i.hasNext()) {
                int [] rel = (int []) i.next();
                //System.out.println("org:"+rel[0]+" dest:"+rel[1]);
                
                Edge e = g.addEdge(g.getNode(rel[0]), g.getNode(rel[1]));
                e.set(IVC.EDGE_TYPE_ATTRIBUTE, IVC.EDGE_RELATIONAL);
            }
        }

        return g;
    }

    public void readArff(String fileLocation) throws DataIOException {

        // Inicialização de variáveis
        m_data = null;
        m_classes = null;

        try {
            FileReader reader = new FileReader(fileLocation);
            m_data = new Instances(reader);
            System.out.println("Reading dataset: " + m_data.relationName());
            System.out.println("Número atributos: " + m_data.numAttributes());
            System.out.println("Número instâncias: " + m_data.numInstances());

            // O último atributo é a classe
            m_classIndex = m_data.numAttributes() - 1;
            m_data.setClassIndex(m_classIndex); // starts with 0
            System.out.println("ClassIndex is: " + m_classIndex);
            System.out.println("Número classes: " + m_data.numClasses());

            m_classes = new ArrayList();
            m_classes.clear();
            for (int i=0; i < m_data.numInstances(); i++)
            {
                Instance ins = m_data.instance(i);

                //System.out.println("inst: " + i + " atrib 1: " + atr.name() + " atrib 1: " + ins.toString(0) + " classe: " + ins.toString(m_classIndex));

                if (!m_classes.contains(ins.toString(m_classIndex)))
                    m_classes.add(ins.toString(m_classIndex));
            }

            for (int i=0; i < m_data.numAttributes(); i++)
            {
                Attribute atr = m_data.attribute(i);
                System.out.println("atr: " + i + "attrib name: " + atr.name());
            }

            /*ArrayList cl = new ArrayList(m_classes);
            while(!cl.isEmpty())
            {
                System.out.println("Classe: "+(String)cl.get(0));
                cl.remove(0);
            }*/

        } catch ( Exception e ) {
            if ( e instanceof DataIOException ) {
                throw (DataIOException)e;
            } else {
                throw new DataIOException(e);
            }
        }

    }


    public void writeGraphML(String fileLocation, int[] clusterAssignments, int numClusters, int[] clusterVisualMapping, ArrayList relationalData) throws DataIOException {

    try {
            System.out.println("Comecei!");
            FileOutputStream fos = new FileOutputStream(fileLocation);

            // Inicia a geração do XML
            OutputFormat of = new OutputFormat("XML","UTF-8",true);
            of.setIndent(1);
            of.setIndenting(true);
            //of.setDoctype(null,"users.dtd");
            XMLSerializer serializer = new XMLSerializer(fos,of);
            // SAX2.0 ContentHandler.
            ContentHandler hd = serializer.asContentHandler();
            hd.startDocument();
            // Processing instruction sample.
            //hd.processingInstruction("xml-stylesheet","type=\"text/xsl\" href=\"users.xsl\"");

            // atributos graphml
            AttributesImpl atts = new AttributesImpl();
            AttributesImpl subAtts = new AttributesImpl();
            atts.clear();
            atts.addAttribute("","","xmlns","CDATA","http://graphml.graphdrawing.org/xmlns");
            // tag graphml - início
            hd.startElement("","","graphml",atts);

            // tag graph - início
            atts.clear();
            atts.addAttribute("","","edgedefault","CDATA","undirected"); // TODO Melhoria: edgedefault deve ser parametrizável
            hd.startElement("","","graph",atts);
            

            // Tratamento do data schema (tags key) - ligações
            // TODO Melhoria: atributos das ligações devem ser dinâmicos
            atts.clear();
            atts.addAttribute("","","id","CDATA","type");   // Guarda o tipo da ligação 0 - clustering; 1 -relacional
            atts.addAttribute("","","for","CDATA","edge");
            atts.addAttribute("","","attr.name","CDATA","type");
            atts.addAttribute("","","attr.type","CDATA","string");
            hd.startElement("","","key",atts);
            hd.endElement("","","key");

            // Tratamento do data schema (tags key) - nós
            if (m_includeInstanceInfo) {
                atts.clear();
                atts.addAttribute("","","id","CDATA",IVC.INSTANCE_ATTRIBUTE);
                atts.addAttribute("","","for","CDATA","node");
                atts.addAttribute("","","attr.name","CDATA",IVC.INSTANCE_ATTRIBUTE);
                atts.addAttribute("","","attr.type","CDATA","int");

                hd.startElement("","","key",atts);
                hd.endElement("","","key");
            }
            for (int i=0; i < m_data.numAttributes(); i++)
            {
                Attribute atr = m_data.attribute(i);

                atts.clear();
                atts.addAttribute("","","id","CDATA",atr.name());
                atts.addAttribute("","","for","CDATA","node");
                atts.addAttribute("","","attr.name","CDATA",atr.name());
                atts.addAttribute("","","attr.type","CDATA","string");      // TODO colocar tipo do atributo dinâmico

                hd.startElement("","","key",atts);
                hd.endElement("","","key");
            }


            Integer nodeId = 0;
            String chr, key = "-1", key2;


            // ****************** Processamento dos nós
            // Criação do nó agregador (para unir os nós dos centros dos grupos)
            /*nodeId = nodeId + 1;
            atts.clear();
            atts.addAttribute("","","id","CDATA", nodeId.toString());
            hd.startElement("","","node",atts);

            // O primeiro atributo de cada nó será o índice da instância (como este nó não é de uma instância, o valor é -1)
            if (m_includeInstanceInfo) {
                subAtts.clear();
                subAtts.addAttribute("","","key","CDATA",IVC.INSTANCE_ATTRIBUTE);
                hd.startElement("","","data",subAtts);
                hd.characters(key.toCharArray(),0,key.length());
                hd.endElement("","","data");
            }

            for (int i=0; i < m_data.numAttributes(); i++)
            {
                Attribute atr = m_data.attribute(i);

                subAtts.clear();
                subAtts.addAttribute("","","key","CDATA",atr.name());

                hd.startElement("","","data",subAtts);
                hd.characters(key.toCharArray(),0,key.length());
                hd.endElement("","","data");
            }
            hd.endElement("","","node");*/


            // Criação do nós dos centros dos grupos
            for (int i=0; i < m_data.numClasses(); i++)
            {
                int k = (i*-1)-1;
                key2 = String.valueOf(k);

                nodeId = nodeId + 1;
                atts.clear();
                atts.addAttribute("","","id","CDATA",nodeId.toString());
                hd.startElement("","","node",atts);

                // O primeiro atributo de cada nó será o índice da instância
                // (como este nó não é de uma instância, mas de um centro, o valor é negativo)
                subAtts.clear();
                subAtts.addAttribute("","","key","CDATA",IVC.INSTANCE_ATTRIBUTE);
                hd.startElement("","","data",subAtts);
                hd.characters(key2.toCharArray(),0,key2.length());
                hd.endElement("","","data");
                
                for (int j=0; j < m_data.numAttributes(); j++)
                {
                    Attribute atr = m_data.attribute(j);

                    subAtts.clear();
                    subAtts.addAttribute("","","key","CDATA",atr.name());
                    hd.startElement("","","data",subAtts);
                    hd.characters(key.toCharArray(),0,key.length());
                    hd.endElement("","","data");
                }
                hd.endElement("","","node");
            }

            // Criação dos nós dos objectos
            for (int i=0; i < m_data.numInstances(); i++)
            {
                nodeId = nodeId + 1;
                Instance ins = m_data.instance(i);

                atts.clear();
                atts.addAttribute("","","id","CDATA",nodeId.toString());
                hd.startElement("","","node",atts);

                // O primeiro atributo de cada nó será o índice da instância
                if (m_includeInstanceInfo) {
                    subAtts.clear();
                    subAtts.addAttribute("","","key","CDATA",IVC.INSTANCE_ATTRIBUTE);
                    hd.startElement("","","data",subAtts);
                    hd.characters(String.valueOf(i).toCharArray(),0,String.valueOf(i).length());
                    hd.endElement("","","data");
                }

                // Adiciona todos os atributos da instância
                for (int j=0; j < m_data.numAttributes(); j++)
                {
                    Attribute atr = ins.attribute(j);

                    //System.out.println("inst: " + i + "" + atr.name() + " atrib 1: " + String.valueOf(ins.value(j)) + ";" + ins.toString(j) + " classe: " + ins.toString(4));

                    subAtts.clear();
                    subAtts.addAttribute("","","key","CDATA",atr.name());
                    hd.startElement("","","data",subAtts);
                    hd.characters(ins.toString(j).toCharArray(),0,ins.toString(j).length());
                    hd.endElement("","","data");
                }
                hd.endElement("","","node");
            }


            Integer source, target;
            // Criação das ligações do nó central aos centros dos grupos
            /*for (int i=0; i < m_data.numClasses(); i++)
            {
                atts.clear();
                target = i+2;
                atts.addAttribute("","","source","CDATA","1");
                atts.addAttribute("","","target","CDATA",target.toString());
                hd.startElement("","","edge",atts);
                    subAtts.clear();
                    subAtts.addAttribute("","","key","CDATA",IVC.EDGE_TYPE_ATTRIBUTE);
                    hd.startElement("","","data",subAtts);
                    chr = IVC.EDGE_RELATIONAL;
                    hd.characters(chr.toCharArray(),0,chr.length());
                    hd.endElement("","","data");
                hd.endElement("","","edge");
            }*/

            // Criação das ligações dos centros de grupo aos objectos a si assignados (ligações do agrupamento)
            if (clusterAssignments == null) {

                int clusterInst = Math.round(m_data.numInstances()/m_data.numClasses());
                /*int cluster = 1;

                for (int i=0; i < m_data.numInstances(); i++)
                {
                    if (clusterInst == i/cluster &&
                        cluster < m_data.numClasses())
                        cluster++;

                    atts.clear();
                    target = i+m_data.numClasses()+1;

                    System.out.println("Ligação: classe:"+ m_data.instance(i).toString(m_classIndex) + " indice: " + m_classes.indexOf(m_data.instance(i).toString(m_classIndex)) );
                    //source = m_classes.indexOf(m_data.instance(i).toString(m_classIndex))+ 2;
                    //source = 1;
                    source = cluster;

                    atts.addAttribute("","","source","CDATA",source.toString());
                    atts.addAttribute("","","target","CDATA",target.toString());
                    hd.startElement("","","edge",atts);
                        subAtts.clear();
                        subAtts.addAttribute("","","key","CDATA",IVC.EDGE_TYPE_ATTRIBUTE);
                        hd.startElement("","","data",subAtts);
                        chr = String.valueOf(IVC.EDGE_CLUSTERING);
                        hd.characters(chr.toCharArray(),0,chr.length());
                        hd.endElement("","","data");
                    hd.endElement("","","edge");
                }*/

            } else {

                for (int i=0; i < m_data.numInstances(); i++)
                {

                    // Obtém a correspondência do cluster na instância para o cluster visual
                    int visualCluster = clusterVisualMapping[clusterAssignments[i]];

                    System.out.println("Ligação: classe:"+ m_data.instance(i).toString(m_classIndex) + " indice: " + clusterAssignments[i] //m_classes.indexOf(m_data.instance(i).toString(m_classIndex))
                            );

                    //source = i+2;
                    source = visualCluster*-1;
                    target = i+m_data.numClasses()+1;

                    atts.clear();
                    atts.addAttribute("","","source","CDATA",source.toString());
                    atts.addAttribute("","","target","CDATA",target.toString());
                    hd.startElement("","","edge",atts);
                        subAtts.clear();
                        subAtts.addAttribute("","","key","CDATA",IVC.EDGE_TYPE_ATTRIBUTE);
                        hd.startElement("","","data",subAtts);
                        chr = IVC.EDGE_CLUSTERING;
                        hd.characters(chr.toCharArray(),0,chr.length());
                        hd.endElement("","","data");
                    hd.endElement("","","edge");

                }
            }

            // Criação das ligações relacionais
            if (relationalData != null) {

                int[] rel;

                Iterator it = relationalData.iterator();
                while (it.hasNext()) {
                    rel = (int[]) it.next();

                    source = rel[0]+m_data.numClasses()+1;
                    target = rel[1]+m_data.numClasses()+1;

                    System.out.println("Ligação relacional: origem:"+source+" destino:"+target);

                    atts.clear();
                    atts.addAttribute("","","source","CDATA", String.valueOf(source));
                    atts.addAttribute("","","target","CDATA", String.valueOf(target));
                    hd.startElement("","","edge",atts);
                        subAtts.clear();
                        subAtts.addAttribute("","","key","CDATA",IVC.EDGE_TYPE_ATTRIBUTE);
                        hd.startElement("","","data",subAtts);
                        chr = IVC.EDGE_RELATIONAL;
                        hd.characters(chr.toCharArray(),0,chr.length());
                        hd.endElement("","","data");
                    hd.endElement("","","edge");

                }

            }


            // tag graph - final
            hd.endElement("","","graph");

            // tag graphml - final
            hd.endElement("","","graphml");


            // Fim do documento e fecho do ficheiro
            hd.endDocument();
            fos.close();

            System.out.println("Acabei!");

        } catch ( Exception e ) {
            if ( e instanceof DataIOException ) {
                throw (DataIOException)e;
            } else {
                throw new DataIOException(e);
            }
        }

    }
    
    // TODO Melhoria: Implementar para os vários métodos. Mudar lógica para a classe IVC e VisualInstances
    private class Relation {
        
        public int m_source;
        public int m_target;
        public int m_sameClass;
        
        public Relation (int source, int target, int sameClass) {
            m_source = source;
            m_target = target;
            m_sameClass = sameClass;
        }
    }


    public void writeArffRelationalEdges(String fileLocation) throws DataIOException {

        // Modelo probabilístico de geração de ligações relacionais
        int source, target;
        double rand, sameClass = 0.20, difClass = 0.05;
        Relation rel;
        ArrayList relations = new ArrayList();

        for (source = 0; source < m_data.numInstances(); source++)
        {
            for (target = source+1; target < m_data.numInstances(); target++)
            {
                rel= null;
                rand = Math.random();

                if (m_data.instance(source).classValue() == m_data.instance(target).classValue()) {
                    //System.out.println("mesma! rand:" + rand);
                    if (rand <= sameClass) {
                        //System.out.println(" - adicionou! origem:" + source + " destino:" + target);
                        rel = new Relation(source, target, 1);
                    }
                }
                else {
                    //System.out.println("diferente! rand:" + rand);
                    if (rand <= difClass) {
                        //System.out.println(" - adicionou! origem:" + source + " destino:" + target);
                        rel = new Relation(source, target, -1);
                    }
                }
                
                if (rel != null && !relations.contains(rel))
                    relations.add(rel);
            }

        }

        String line;
        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream(fileLocation);

            Iterator it = relations.iterator();
            while (it.hasNext())
            {
                rel = (Relation) it.next();
                line = rel.m_source+"\t" +         // Objecto origem
                       rel.m_target+"\t" +         // Objecto destino
                       rel.m_sameClass;            // 1 Pertencem à mesma classe; -1 Não pertencem à mesma classe
                if (it.hasNext())
                    line += "\n";
                fos.write(line.getBytes());
            }

            fos.flush();
            fos.close();
        } catch ( Exception e ) {
            if ( e instanceof DataIOException ) {
                throw (DataIOException)e;
            } else {
                throw new DataIOException(e);
            }
        }

    }

    public ArrayList readRelations(String fileLocation) {
        
        ArrayList relations = new ArrayList();

        try {
            BufferedReader reader = new BufferedReader(new FileReader(fileLocation));
            String s = null;
            int[] rel = null;

            while ((s = reader.readLine()) != null) {
                StringTokenizer tokenizer = new StringTokenizer(s);
                rel = new int[3];
                int i = 0;
                while (tokenizer.hasMoreTokens()) {
                    String token = tokenizer.nextToken();

                    rel[i] = Integer.parseInt(token);

                    if (i == 0) {
                        System.out.println("Source instance: " + Integer.parseInt(token));
                    } else if (i == 1) {
                        System.out.println("Target instance: " + Integer.parseInt(token));
                    } else if (i == 2) {
                        System.out.println("Same class: "+ Integer.parseInt(token));
                    }

                    i++;
                }

                if (!relations.contains(rel)) {
                    relations.add(rel);
                }
            }
        } catch (Exception e) {
                System.out.println("Problems reading from relations file: " + e);
                e.printStackTrace();
        }

        return relations;
    }

    public void writeArffConstraints(String fileLocation, ArrayList constraints) throws DataIOException {

        if (constraints != null)
        {
            String line;
            FileOutputStream fos = null;

            try {
                fos = new FileOutputStream(fileLocation);

                String[] constraint;
                Iterator it = constraints.iterator();
                while (it.hasNext())
                {
                    constraint = (String[]) it.next();
                    line = constraint[0]+"\t" +         // Objecto origem
                           constraint[1]+"\t" +         // Objecto destino
                           constraint[2];               // Tipo de restrição 1 MUST_LINK; -1 CANNOT_LINK
                    if (it.hasNext())
                        line += "\n";
                    fos.write(line.getBytes());
                }

                fos.flush();
                fos.close();
            } catch ( Exception e ) {
                if ( e instanceof DataIOException ) {
                    throw (DataIOException)e;
                } else {
                    throw new DataIOException(e);
                }
            }
        }

    }
    

    public static void main(String[] args) throws DataIOException {

        ArffReader r = new ArffReader();
        r.readArff("/home/vhbarros/Dropbox/Tese/Software/preka/data/overlappingcircles.arff");
        r.writeArffRelationalEdges("/home/vhbarros/Dropbox/Tese/Software/preka/data/overlappingcircles.arff.relations");
        //ArrayList relationalData = r.readRelations("/home/vhbarros/Dropbox/Tese/Software/preka/data/overlappingcircles.arff.relations");
        //r.writeGraphML("/home/vhbarros/Dropbox/Tese/Software/preka/data/overlappingcircles.arff.xml", null, 0, null, relationalData);
        //r.writeArffConstraints("/home/vhbarros/Dropbox/Tese/Software/preka/data/teste.constraints", null);


    }

}
